using System;
using System.Collections;
using System.Collections.Generic;
using System.Html;
using System.Runtime.CompilerServices;
using System.Serialization;
using Awaiter;
using Minions.Classes;
using Nsb.Classes;
using jQueryApi;

namespace MinionsEdit.Classes
{
    [IgnoreNamespace]
    public class MinionEdit : Minion
    {
        public const string GigMe = "GigMe";
        public const string DotGigMe = "." + GigMe;
        public const double MinSize = 20;

        public const string MaskOnEv = "MaskOnEv";
        public const string MaskOffEv = "MaskOffEv";
        public const string MaskSleepEv = "MaskSleepEv";
        public const string MaskWakeEv = "MaskWakeEv";
        public const string MaskRubberbandEv = "MaskRubberbandEv";
        public const string UploadImgEv = "UploadImgEv";
        public const string PageManagerEv = "PageManagerEv";
        private static MinionEdit _activeFocus;
        public static Dictionary EditKeys; // Must come before InstallEvents()
        public static bool MaskSleeping;
        private readonly Dictionary _dragOptions;
        private readonly jQueryObject _snapToNone = (jQueryObject) Script.Literal("jQuery()");
        private bool _dropTarget;
        private MiMetrics _mx;

        static MinionEdit()
        {
            Evo.Bind(MaskOnEv, new Evo().Fn((Action<MinionEdit>) MiMask.MaskOn));
            Evo.Bind(MaskOffEv, new Evo().Fn((Action) MiMask.MaskOff));
            Evo.Bind(MaskSleepEv, new Evo().Fn((Action) MiMask.MaskSleep));
            Evo.Bind(MaskWakeEv, new Evo().Fn((Action<MinionEdit>) MiMask.MaskWake));
            Evo.Bind(MaskRubberbandEv, new Evo().Fn((Action<MinionEdit>) MiMask.MaskRubberband));
            //Evo.Bind(UploadImgEv, new Evo().Fn());
            Evo.Bind(PageManagerEv, new Evo().Fn((Action<jQueryEvent>) PagesEv));

            MiKeyMap.InstallEvents();
        }

        public MinionEdit()
        {
            Editing = true;
            _dragOptions = new Dictionary("start", (Action<jQueryEvent, jQueryUiObject>) DragStart, "drag", (Action<jQueryEvent, jQueryUiObject>) DragDrag, "stop", (Action<jQueryEvent, jQueryUiObject>) DragStop
                //"helper", "clone",
                //"appendTo", HostElement
                );
        }

        #region Finders

        // ReSharper disable CSharpWarnings::CS0108
        public static MinionEdit GetMinionById(string id)
            // ReSharper restore CSharpWarnings::CS0108
        {
            return (MinionEdit) Minion.GetMinionById(id);
        }

        public static MinionEdit GetMinionFromEl(jQueryObject el)
        {
            return GetMinionById(el.GetAttribute("id"));
            //return (Minion)el.GetDataValue(MinionClassName) ?? null;
        }

        public static MinionEdit GetMinionFromEv(jQueryEvent e)
        {
            try
            {
                return GetMinionFromEl(jQuery.FromElement(e.Target).Closest(DotMinionClassName));
            }
            catch
            {
#if DEBUG
                Script.Literal("debugger");
#endif
                return null;
            }
        }

        #endregion Finders

        #region Properties

        public MinionEdit TopMinionEdit
        {
            get { return (MinionEdit) TopMinion; }
        }

        public bool DropTarget
        {
            get { return _dropTarget; }
            set { _dropTarget = value; }
        }

        public static MinionEdit ActiveFocus
        {
            get { return _activeFocus; }
            private set { _activeFocus = value; }
        }

        public static MinionEdit ActiveParent
        {
            get { return _activeFocus != null ? (MinionEdit) _activeFocus.Parent : null; }
        }

        public static Dictionary<string, Minion> ActiveChildren
        {
            get { return _activeFocus != null ? _activeFocus.Parent.Children : null; }
        }

        public static MinionData ActiveData
        {
            get { return _activeFocus != null ? _activeFocus.LiveData : null; }
        }

        public static MiCanvas ActiveCanvas
        {
            get { return _activeFocus != null ? _activeFocus.TheCanvas : null; }
        }

        public static MiCanvasData ActiveCanvasData
        {
            get { return ActiveCanvas != null ? ActiveCanvas.CanvasData : null; }
        }

        private jQueryObject ParentElement
        {
            get { return Parent != null ? Parent.Element : null; }
        }

        public static bool FocusOrFlash
        {
            get
            {
                if (ActiveFocus == null)
                {
                    FlashWindow();
                    return false;
                }
                return true;
            }
        }

        public static bool CanvasOrFlash
        {
            get
            {
                if (ActiveFocus == null || ActiveFocus.TopMinionEdit == null)
                {
                    FlashWindow();
                    return false;
                }
                return true;
            }
        }

        public bool HasFocus
        {
            get { return ActiveFocus == this; }
        }

        public string SnapShot
        {
            get
            {
                MinionData md = LiveData;
                return Json.Stringify(md);
            }
        }

        #endregion Properties

        #region Methods

        public static void PagesEv(jQueryEvent e)
        {
            if (!e.ShiftKey)
            {
                GlassOff();
                Pages.Ev(e);
            }
        }

        public override Minion NewMinion(Minion top)
        {
            return new MinionEdit().SetTop(top);
        }

        public void Insert(Minion before, Minion after)
        {
            before.Element.InsertBefore(after.Element);
            new Evo().FireWith(Resort);
        }

        public void CopyMinion(bool group)
        {
            MinionPaste.Copy(this);
        }

        public override void DeleteMinion(bool immediate)
        {
            Evo.Trigger(MaskOffEv);
            base.DeleteMinion(immediate);
        }

        protected override void Draggable()
        {
            Element.Plugin<Jewl>().Draggable(_dragOptions);
        }

        protected override void Droppable()
        {
            MiDrop.MakeDroppable(this);
        }

        private void RemFromParent()
        {
            if (Parent != null)
            {
                Type.DeleteField(Parent.Children, Id);
            }
            Parent = null;
        }

        private void DragStart(jQueryEvent e, jQueryUiObject ui)
        {
            Inform.Debug("MinionEdit.DragStart({0})", Id);
            Evo.Trigger(MaskSleepEv);
            Evo.Trigger(MaskRubberbandEv, this);
            _mx = new MiMetrics().SnapDragStart(e, ui);
        }

        private void DragDrag(jQueryEvent e, jQueryUiObject ui)
        {
            _mx.SnapDragDrag(e);
        }

        private void DragStop(jQueryEvent e, jQueryUiObject ui)
        {
            Inform.Debug("MinionEdit.DragStop()");
            _mx.SnapDragStop(e);
            Evo.Trigger(MaskWakeEv, this);
            if (Type.HasField(Window.Self, "TestRepaintAll"))
            {
                // Only for testing to show that the drag positions stick - TODO: Remove for production
                Window.SetTimeout(delegate
                                  {
                                      new Evo().FireWith(RePaintCanvas);
                                      Evo.Trigger(MaskOnEv, this);
                                  }, 1);
            }
            new Evo().FireWith(Save);
        }

        public void RePaintAndSave(Evo evo)
        {
            RePaint(evo);
            new Evo().FireWith(Save);
        }

        public void ResizeFixedChildMetrics(double ratio)
        {
            foreach (KeyValuePair<string, MinionEdit> pair in (Dictionary<string, MinionEdit>) (object) Children)
            {
                MinionData md = pair.Value.Data;
                if (!md.relative)
                {
                    md.dim.width *= ratio;
                    md.dim.height *= ratio;
                    md.pos.Left *= ratio;
                    md.pos.Top *= ratio;
                    pair.Value.PaintMetrics();
                }
                pair.Value.ResizeFixedChildMetrics(ratio);
            }
        }

        #endregion Methods

        #region Chains

        /// <summary>
        /// Erases from dom and parent. The reverse of Paint()
        /// </summary>
        public MinionEdit Erase()
        {
            Element.Empty().Remove();
            RemFromParent();
            return this;
        }

        public MinionEdit RestoreFrom(string snapshot)
        {
            Evo.Trigger(MaskOffEv, this);
            jQueryObject placeholder = jQuery.FromHtml("<div/>").InsertAfter(Element);
            Element.Empty();
            SetData((MinionData) Json.Parse(snapshot)).Paint();
            Element.InsertBefore(placeholder);
            placeholder.Remove();
            return this;
        }

        public MinionEdit InstallPageGear()
        {
            MiPageGear.InstallPageGear();
            return this;
        }

        public MinionEdit ConvertToRelativeMetrics(Minion parent)
        {
            if (!Data.relative && parent != null)
            {
                // TODO: Also make css values relative by converting and adding % sign
                Data.relative = true;
                Data.offsetParentWidth = Element.OffsetParent().GetWidth();
                Data.offsetParentHeight = Element.OffsetParent().GetHeight();
                //Data.css["font-size"] = Number.ParseDouble((string)Data.css["font-size"] ?? "7.5pt") / Data.initialWidth * Data.dim.width + "pt";
                Units = "%";
                //jQueryObject op = parent.Element;
                //double rw = 100/(double) op.GetWidth();
                //double rh = 100/(double) op.GetHeight();
                //Data.pos.Left = Data.pos.Left*rw;
                //Data.pos.Top = Data.pos.Top*rh;
                //Data.dim.width = Data.dim.width*rw;
                //Data.dim.height = Data.dim.height*rh;
                PaintMetrics();
                foreach (KeyValuePair<string, MinionEdit> pair in (Dictionary<string, MinionEdit>) (object) Children)
                {
                    pair.Value.ConvertToRelativeMetrics(this);
                }
            }
            return this;
        }

        public MinionEdit ResizeFixedMetrics(Minion newParent)
        {
            if (newParent == null)
                return this;
            if (!Data.relative)
            {
                double nw = newParent.Element.GetWidth();
                double nh = newParent.Element.GetHeight();
                if (Data.dim.width + 20 > nw || Data.dim.height + 20 > nh)
                {
                    double ratio;
                    if (Data.dim.width > Data.dim.height)
                    {
                        // Set dim at 50% of host and center position
                        ratio = nw/2/Data.dim.width;
                        Data.dim.width = nw/2;
                        Data.dim.height = Data.dim.height*ratio;
                        Data.pos.Top = (nh - Data.dim.height) / 2;
                        Data.pos.Left = nw / 4;
                    }
                    else
                    {
                        // Set dim at 50% of host and center position
                        ratio = nh / 2 / Data.dim.height;
                        Data.dim.height = nh / 2;
                        Data.dim.width = Data.dim.width * ratio;
                        Data.pos.Top = nh / 4;
                        Data.pos.Left = (nw - Data.dim.width) / 2;
                    }
                    ResizeFixedChildMetrics(ratio);
                }
                else
                {
                    // Center position only
                    Data.pos.Left = (nw - Data.dim.width)/2;
                    Data.pos.Top = (nh - Data.dim.height)/2;
                }
                //ConvertToRelativeMetrics(newParent);
                PaintMetrics();
            }
            else
            {
                // TODO: make work with both width and height, not just width.
                double ratio = Data.offsetParentWidth > 0 ? (newParent.Element.GetWidth()/Data.offsetParentWidth) : 1;
                ResizeFixedChildMetrics(ratio);
            }
            return this;
        }

        #endregion Chains

        #region Events

        public static void MouseOverEv(jQueryEvent e)
        {
            //Inform.Debug("MinionEdit.MouseOverEv()");
            CancelEvent(e);
            if (!MaskSleeping)
            {
                MinionEdit mm = GetMinionFromEv(e);
                if (mm.Element.GetWidth() >= MinSize)
                {
                    ActiveFocus = mm;
                    Evo.Trigger(MaskOnEv, ActiveFocus);
                }
            }
        }

        public static void MouseOutEv(jQueryEvent e)
        {
            CancelEvent(e);
            if (!MaskSleeping)
            {
                Evo.Trigger(MaskOffEv);
                ActiveFocus = null;
            }
        }

        public void UndoEv(jQueryEvent e)
        {
            CancelEvent(e);
            UndoItem ui = (UndoItem) Undo.Prior(PartitionKey);
            if (ui != null)
                new Evo().FireWith(RePaintCanvas);
        }

        public void RedoEv(jQueryEvent e)
        {
            CancelEvent(e);
            UndoItem ui = (UndoItem) Undo.Next(PartitionKey);
            if (ui != null)
                new Evo().FireWith(RePaintCanvas);
        }

        public static void ActiveDeleteMinionEv(jQueryEvent e)
        {
            CancelEvent(e);
            if (FocusOrFlash)
                ActiveFocus.DeleteMinion(e.ShiftKey);
        }

        public static void ActiveCopyEv(jQueryEvent e)
        {
            CancelEvent(e);
            if (FocusOrFlash)
                ActiveFocus.CopyMinion(e.ShiftKey);
        }

        public static void ActiveMakeRelativeEv(jQueryEvent e)
        {
            CancelEvent(e);
            if (FocusOrFlash && ActiveFocus.Parent != null)
                ActiveFocus.ConvertToRelativeMetrics(ActiveFocus.Parent);
        }

        public static void ActivePasteEv(jQueryEvent e)
        {
            CancelEvent(e);
            if (FocusOrFlash)
                new MinionPaste(ActiveFocus).PasteEv(e);
        }

        public static void ActiveRepaintEv(jQueryEvent e)
        {
            CancelEvent(e);
            foreach (KeyValuePair<string, Minion> pair in All)
            {
                if (pair.Value.Parent == null)
                    new Evo().FireWith(pair.Value.RePaintCanvas);
            }
            FlashWindow("green");
        }

        public void RefreshPageGear(jQueryEvent e)
        {
        }

        #endregion Events

        #region Evokes

        public override void RePaint(Evo evo)
        {
            if (ActiveFocus != null && ActiveFocus.Id == Id)
                ActiveFocus = null;
            Evo.Trigger(MaskOffEv);
            base.RePaint(evo);
        }

        public void Resize(Evo evo)
        {
            //Script.Literal("debugger");
            Inform.Debug("MinionEdit.Resize({0})", Id);
            _mx = new MiMetrics().SnapResizeStart(evo.Event);
        }

        public void Save(Evo evo)
        {
            Unsaved = true;
            if (PartitionKey == null)
                new Evo().Response(delegate { Unsaved = false; }).Evoke(TopMinionEdit.SaveAw).SetValue(Undo.AllowUndo).Fire(Await.AsyncAw);
            else
                new Evo().Response(delegate { Unsaved = false; }).Evoke(SaveMeAw).SetValue(Undo.AllowUndo).Fire(Await.AsyncAw);
        }

        public virtual void SaveAw(Evo evo) // Value = (bool)noUndo
        {
            // TODO: save method for when this is not a canvas goes here
        }

        public void SaveMeAw(Evo evo)
        {
            // TODO: save method for saving this minion by itself goes here
        }

        public void ToFront(Evo evo)
        {
            if (Parent != null)
            {
                Element.AppendTo(ParentElement);
                Resort(evo);
            }
        }

        public void ToBack(Evo evo)
        {
            if (Parent != null)
            {
                Element.PrependTo(ParentElement);
                Resort(evo);
            }
        }

        public void ToHigher(Evo evo)
        {
            Element.Next(DotMinionClassName).InsertBefore(Element);
        }

        public void ToLower(Evo evo)
        {
            Element.Prev(DotMinionClassName).InsertAfter(Element);
        }

        public void Resort(Evo evo)
        {
            // Resort Minions into actual document order
            if (Parent != null)
            {
                Dictionary<string, Minion> md = new Dictionary();
                ParentElement.Children(DotMinionClassName).Each(
                    delegate(int i, Element domEl)
                    {
                        Minion mm = GetMinionFromEl(jQuery.FromElement(domEl));
                        md[mm.Id] = mm;
                    });

                Parent.Children = md;
            }
        }

        #endregion Evokes

        #region Modifiers

        public void GetCss(Evo evo)
        {
            string name = evo.Name;
            evo.Value = Data.css[name] ?? null;
            Inform.Debug("GetCss getting {0}={1}", name, evo.Value);
        }

        public void SetCss(Evo evo)
        {
            string name = evo.Name;
            Dictionary css;
            if (Type.GetScriptType(evo.Value) == "string")
            {
                // ReSharper disable PossibleInvalidCastException
                if ("remove" == (string) evo.Value)
                    // ReSharper restore PossibleInvalidCastException
                {
                    // Delete the value
                    Type.DeleteField(Data.css, name);
                    Element.CSS(name, "");
                    Inform.Debug("SetCss removing {0}", name);
                    return;
                }

                css = new Dictionary();
                css[name] = evo.Value;
            }
            else
                css = (Dictionary) evo.Value;
            jQuery.Extend(Data.css, css);
            MapCss(Data, Element, css);
            Inform.Debug("SetCss setting {0}={1}", name, Rx.PrettyJson(css));
        }

        #endregion Modifiers
    }
}